Web Vitals & React Performance Debugging Guide
Core Web Vitals Overview
Largest Contentful Paint (LCP)
- What: Measures loading performance - time until largest content element is visible
- Target: Under 2.5s
- Debugging:
import { getLCP } from 'web-vitals';
getLCP(console.log); // Basic logging
getLCP((metric) => {
// Send to analytics
analytics.send({
name: 'LCP',
value: metric.value,
id: metric.id,
element: metric.element // The actual LCP element
});
});
First Input Delay (FID)
- What: Measures interactivity - time from user's first interaction to browser's response
- Target: Under 100ms
- Debugging:
import { getFID } from 'web-vitals';
getFID((metric) => {
console.log({
name: 'FID',
value: metric.value,
eventTarget: metric.eventTarget, // Element that was interacted with
eventType: metric.eventType // Type of interaction
});
});
Cumulative Layout Shift (CLS)
- What: Measures visual stability - unexpected layout shifts
- Target: Under 0.1
- Debugging:
import { getCLS } from 'web-vitals';
getCLS((metric) => {
console.table(metric.entries.map(entry => ({
startTime: entry.startTime,
value: entry.value,
hadRecentInput: entry.hadRecentInput,
elements: entry.sources.map(source => source.node)
})));
});
React Performance Debugging Tools
React Developer Tools
// Enable React Developer Tools in development
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}
Custom Performance Hooks
import { useEffect, useRef } from 'react';
function useComponentTimer(componentName) {
const startTime = useRef();
useEffect(() => {
startTime.current = performance.now();
return () => {
const duration = performance.now() - startTime.current;
console.log(`${componentName} mounted for ${duration}ms`);
};
}, [componentName]);
}
Web Vitals in Next.js
// pages/_app.js
import { useEffect } from 'react';
import * as webVitals from 'web-vitals';
function MyApp({ Component, pageProps }) {
useEffect(() => {
webVitals.getCLS(console.log);
webVitals.getFID(console.log);
webVitals.getLCP(console.log);
webVitals.getFCP(console.log);
webVitals.getTTFB(console.log);
}, []);
return <Component {...pageProps} />;
}
Common Performance Issues & Solutions
Image Optimization
// Next.js Image component with optimization
import Image from 'next/image';
function OptimizedImage() {
return (
<Image
src="/large-image.jpg"
width={800}
height={600}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
priority={true} // For LCP images
/>
);
}
Code Splitting
// Using React.lazy for component-level code splitting
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}
Performance Monitoring
// Using Performance Observer API
const performanceObserver = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.entryType === 'largest-contentful-paint') {
console.log('LCP:', entry.startTime);
console.log('Element:', entry.element);
}
});
});
performanceObserver.observe({ entryTypes: ['largest-contentful-paint'] });
Debug Checklist
-
Install Chrome DevTools extensions:
- React Developer Tools
- Performance insights
- Lighthouse
-
Enable Performance Tracing:
// Add to your app's entry point
if (process.env.NODE_ENV !== 'production') {
const { enableReactTracking } = require('@performance/react-tracker');
enableReactTracking({
logToConsole: true,
logToNetwork: true
});
}
- Monitor Layout Shifts:
let cumulativeScore = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
cumulativeScore += entry.value;
console.log('Layout shift:', {
value: entry.value,
cumulative: cumulativeScore,
elements: entry.sources.map(source => source.node)
});
}
}
}).observe({ entryTypes: ['layout-shift'] });
- Implement Error Boundaries:
class PerformanceErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
console.error('Performance issue detected:', error);
console.log('Component stack:', errorInfo.componentStack);
// Log to your analytics service
logError({
type: 'PERFORMANCE',
error,
componentStack: errorInfo.componentStack
});
}
render() {
return this.props.children;
}
}